package com.bondar.task1;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FindSinCosSeries {
	private final double step;
	private int precision;

	public FindSinCosSeries(double step) {
		this.step = step;
		this.precision = getPrecisionOfStep(step);
	}
	
	public double calculate(int threads, int N) throws InterruptedException {
		List<Summator> list = new ArrayList<Summator>();
		double threadOffset = threads * step;
		
		ExecutorService executorService = Executors.newFixedThreadPool(threads);
		
		for (int i = 0; i < threads; i++) {
			list.add(new Summator(-N, N, threadOffset, i));
		}
		
		List<Future<Double>> executorResults = executorService.invokeAll(list);
		
		return totalSum(executorResults);
	}

	private double totalSum(List<Future<Double>> sumList)
			throws InterruptedException {
		
		double sum = 0.0;
			try {
				for (Future<Double> future : sumList) {
					sum += future.get();
				}
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
			
		return sum;
	}
	
	private int getPrecisionOfStep(double n) {
		return (int) (1 / n);
	}

	private double roundTo(double n, int to) {
		return (double) Math.round(n * to) / to;
	}

	private class Summator implements Callable<Double> {
		private final double start;
		private final double end;
		private final double threadOffset;
		private int threadIndex;

		public Summator(double taskStart, double taskEnd, double threadOffset, int index) {
			this.start = taskStart;
			this.end = taskEnd;
			this.threadOffset = threadOffset;
			this.threadIndex = index;
		}
		
		@Override
		public Double call() throws Exception {
			double sum = 0.0;
			for (double i = start + threadIndex*step; i <= end; i = roundTo(i + threadOffset, precision)) {
				sum += Math.sin(i) * Math.cos(i);
			}
			return sum;
		}
	}

}
